use std::fs;

use crate::error::*;
use log::debug;
use serde::{Deserialize, Serialize};

#[derive(Default, Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[serde(default)]
pub struct BackendServerConf {
    pub address: String,
    pub port: Option<u16>,
    pub weight: Option<usize>,
    pub max_fails: Option<usize>,
    pub failed_timeout_ms: Option<u64>,
    pub attribute: Option<Vec<String>>,
}

#[derive(Default, Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[serde(default)]
pub struct BackendConf {
    // 当前仅支持static和dns.
    pub sd_type: Option<String>,
    // dns解析结果缓存时间, 默认5min.
    pub sd_ttl_ms: Option<u64>,
    pub servers: Vec<BackendServerConf>,
    pub backup_servers: Vec<BackendServerConf>,
    // 当前仅支持rr和chash.
    pub balance_type: Option<String>,
}

#[allow(dead_code)]
impl BackendConf {
    pub fn load_from_yaml(path: &str) -> Result<Self> {
        let conf_str = fs::read_to_string(path).if_err(
            ErrorType::ParseError,
            format!("Unable to read conf file from {path}"),
        )?;
        debug!("Load {path} backend success");
        Self::from_yaml(&conf_str)
    }

    fn from_yaml(conf_str: &str) -> Result<Self> {
        let mut conf: BackendConf = serde_yaml::from_str(conf_str)
            .if_err(ErrorType::ParseError, "Unable to parse backed")?;

        if let Some(sd_type) = &conf.sd_type {
            if sd_type != "static" && sd_type != "dns" {
                return Error::e_explain(
                    ErrorType::ParseError,
                    "invalid sd_type",
                );
            }
        } else {
            conf.sd_type = Some("static".to_string());
        }

        if conf.sd_ttl_ms.is_none() {
            conf.sd_ttl_ms = Some(5 * 1000 * 60);
        }

        if let Some(balance_type) = &conf.balance_type {
            if balance_type != "rr" && balance_type != "chash" {
                return Error::e_explain(
                    ErrorType::ParseError,
                    "invalid balance_type",
                );
            }
        } else {
            conf.balance_type = Some("rr".to_string());
        }

        for v in conf.servers.iter_mut() {
            if v.address.is_empty() {
                return Error::e_explain(
                    ErrorType::ParseError,
                    "invalid backend server",
                );
            }
        }

        Ok(conf)
    }
}

#[cfg(test)]
mod tests {
    use super::BackendConf;

    #[test]
    fn test_backend_conf() {
        let str = r#"
---
sd_type: static
servers:
  - address: 127.0.0.1
    port: 80
    weight: 10
    max_fails: 3
    failed_timeout_ms: 10000
  
  - address: 127.0.0.1
    port: 88
    weight: 10
    max_fails: 3
    failed_timeout_ms: 10000

backup_servers:
  - address: 10.0.0.1
    port: 80
    weight: 10
    max_fails: 3
    failed_timeout_ms: 10000
  - address: 10.0.0.1
    port: 88
    weight: 10
    max_fails: 3
    failed_timeout_ms: 10000

balance_type: chash
        "#;

        let backend_conf = BackendConf::from_yaml(str).unwrap();
        println!("{:#?}", backend_conf);

        assert_eq!(backend_conf.sd_type, Some("static".to_string()));
        assert_eq!(backend_conf.sd_ttl_ms, Some(300000));
        assert_eq!(backend_conf.servers.len(), 2);
        assert_eq!(backend_conf.backup_servers.len(), 2);
        assert_eq!(backend_conf.balance_type, Some("chash".to_string()));
    }
}
